/***************************************************************************
 * This code was adapted from TSPC_CCID.cs which was originally written by *
 * Teridian SemiConductor, Corp. It has been modified from its original    *
 * version to allow for use in the .NET Micro Framework. Additions have    *
 * been made to the original version to allow a broader use of the reader. *
 * LAST MODIFIED: 2010 April 23                                            *
 * Copyright (C) 2009 Gemalto                                              *
 ***************************************************************************/
/***************************************************************************
 *  Device.cs is part of the PC/SC Micro API for the .NET Micro            *
 *  Framework.                                                             *
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is free software:     *
 *  you can redistribute it and/or modify it under the terms of the GNU    *
 *  General Public License as published by the Free Software Foundation,   *
 *  either version 2 of the License, or (at your option) any later version.*
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is distributed in     *
 *  the hope that it will be useful, but WITHOUT ANY WARRANTY; without even*
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    *
 *  PURPOSE.  See the GNU General Public License for more details.         *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with the PC/SC Micro API for the .NET Micro Framework. If not,   *
 *  see <http://www.gnu.org/licenses/>.                                    *
 ***************************************************************************/
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace PCSCMicro
{
    internal class Device : Reader
    {
        #region Definition of constants and global variables

        // Collecting Data TimeOut Design Setting
        int TestTimeOut = 15 * 60;       // Timeout for APDU calculated to second
        int InitializationTimeOut = 10;  // Apply to non-APDU and non-EMV testing command, in second
        int TimeOutUnitInMiliSec = 500;  // This TimeOut is independent with the ones in Min and Sec

        // Hard-coded configuration constants
        string ACK = "C0AAC0";
        string NACK = "C055C0";
        string PowerOnReader = "C0110000041E05FFFFC0";      //9600 with 30 seconds timeout
        string PowerOnSC = "C03362";
        string PowerOffSC = "C03363";
        string CheckStatus = "C03365";
        string SetParametersCommand = "C03361";
        string APDUCommand = "C0336F";
        
        // Dynamic Strings used for commands
        string Config_Init;
        string SlotID = "00";

        // Slip character definition
        const byte END = 0xC0;
        const byte ESC = 0xDB;
        const byte ESC_END = 0xDC;
        const byte ESC_ESC = 0xDD;
        byte[] ESC_END_ARR = new byte[] { ESC, ESC_END };
        byte[] ESC_ESC_ARR = new byte[] { ESC, ESC_ESC };

        // Keep track of slot status
        const int ACTIVATED = 0;
        const int PRESENT = 1;
        const int NOCARD = 2;
        const int ERROR = 3;
        int SlotStatus = NOCARD;

        // Maximum number of log lines
        const int MaxBytes = 4096;

        // Flags used for running processes
        bool IsSerialPortOn = false;
        bool APDUCommandComplete = true;
        bool ActivatingSmartCard = false;
        bool CardActivated = false;
        bool ResettingCard = false;

        // Retry counter for Serial Port Communication
        int NumberOfRetries;

        // Serial Port
        string port = "COM1";
        int baudrate = 9600;
        System.IO.Ports.SerialPort ComPort = new System.IO.Ports.SerialPort(System.IO.Ports.Serial.COM1);

        // Packet parser
        PacketParser Packet = new PacketParser();

        // PPS Strings
        string Protocol = "00";//Protocol to be set by user later
        string FiDi = "11";//FiDi to be set by user later

        // Card characteristics
        ReaderError error = ReaderError.NONE;
        string atr = null;

        #endregion

        /*****************************************************************************/
        /*                                                                           */
        /*      Serial Port Communication: Initialize, Send and Receive Data         */
        /*                                                                           */
        /*****************************************************************************/

        #region Serial Port Interface

        // Initialize Serial Port 
        void InitializeComPort(int baudrate, System.IO.Ports.Handshake handShake)
        {
            if (!ComPort.IsOpen)
            {
                // Set COM port properties
                ComPort.BaudRate = baudrate;
                ComPort.Parity = System.IO.Ports.Parity.None;
                ComPort.StopBits = System.IO.Ports.StopBits.One;
                ComPort.DataBits = 8;
                ComPort.Handshake = handShake;
                ComPort.ReadTimeout = 5000;
                ComPort.Open();
            }
        }

        // Computer CRC of the sending string
        // CRC is computed using the following formula 
        // over the CMD and Information fields:
        // F(x) = 1 + x^2 + x^15 + x^16
        ushort ComputeCRC(byte[] bArr, int offset, int bytecount)
        {
            ushort CRC = 0;
            byte d;
            int count = 0;
            while (count++ < bytecount)
            {
                d = bArr[count + offset - 1];
                for (int i = 8; i > 0; i--)
                {
                    if (((CRC ^ d) & 1) > 0)
                        CRC = (ushort)((CRC >> 1) ^ 0xA001);
                    else
                        CRC >>= 1;
                    d >>= 1;
                }
            }
            // LittleEndian
            CRC = (ushort)(~CRC);
            return CRC;
        }

        // Check whether byte at bytecount is inside the packet
        bool IsInsidePacket(byte[] byteArray, int length)
        {
            // () is used for current byte
            // (C0)AAC0 or (C0)55C0
            if ((length < byteArray.Length - 2) && (byteArray[length] == 0xC0) && (byteArray[length + 2] == 0xC0))
            {
                return false;
            }
            // C0AA(C0) or C055(C0)
            if ((length > 1) && (byteArray[length] == 0xC0) && (byteArray[length - 2] == 0xC0))
            {
                return false;
            }
            // C0(AA)C0 or C0(55)C0
            if ((length > 0) && (byteArray[length - 1] == 0xC0) && (byteArray[length + 1] == 0xC0))
            {
                return false;
            }
            return true;
        }

        // Check if the packet received is complete
        bool IsComplete(byte[] byteArray, int length)
        {
            // Definition: Complete = ACK + C0 + (Data) + CRC1 + CRC2 + C0 = 3 + 1 + (Data) + 1 + 1 + 1 > 7 bytes
            // or a NACK response (no wait for a NACK)
            if (((length > 7) && (byteArray[length - 1] == 0xC0)) ||
                (HexStringDecoder.ByteArrayToString(byteArray, length) == NACK))
                return true;
            return false;
       }

        // Send an array of bytes to the serial port
        void SendReceive(string Message, ref string Response, int InputTimeOutInSec)
        {
            int RetryNACK = NumberOfRetries;
        SendLabel:   // Used when receiving a NACK response, comand sending must be resent
            /*-----------------------------------*/
            /*     Send data to the Serial Port  */
            /*-----------------------------------*/

            #region Send data to the Serial Port
            //Convert Hex String to Byte Array
            byte[] myBuffer = new byte[Message.Length / 2]; // reserve 2 byte for CRC
            myBuffer = HexStringDecoder.StringToByteArray(Message);
            if ((Message != ACK) && (Message != NACK))
            {
                // CRC Calculation
                ushort CRC = ComputeCRC(myBuffer, 1, myBuffer.Length - 4);
                myBuffer[myBuffer.Length - 2] = (byte)(CRC & 0x00FF);
                myBuffer[myBuffer.Length - 3] = (byte)(CRC >> 8);
            }
            // Update Message to include Error detection bits
            Message = HexStringDecoder.ByteArrayToString(myBuffer, myBuffer.Length);
            ComPort.DiscardInBuffer();
            ComPort.DiscardOutBuffer();
            // Send single byte in the byte array so it can be parsed
            // Check to include escape characters
            for (int i = 0; i < myBuffer.Length; i++)
            {
                if ((myBuffer[i] == 0xC0) && (i != 0) && (i != myBuffer.Length - 1))
                {
                    ComPort.Write(ESC_END_ARR, 0, 2);
                }
                else if ((myBuffer[i] == 0xDB) && (i != 0) && (i != myBuffer.Length - 1))
                {
                    ComPort.Write(ESC_ESC_ARR, 0, 2);
                }
                else
                {
                    ComPort.Write(myBuffer, i, 1);
                }
            }
            #endregion

            /*-----------------------------------*/
            /* Receive data from the Serial Port */
            /*-----------------------------------*/

            #region Receive data from the Serial Port
            // Initialize receive related variables
            int TimeOutCycleCountDown = (InputTimeOutInSec * 1000 / TimeOutUnitInMiliSec);
            bool TimeOut = false;
            int bytecount = 0;
            byte[] byteArrayBuffer = new byte[MaxBytes];
            byte[] byteArrayReturn = new byte[MaxBytes];
            // Reset the data string received and the byte received
            Response = "";
            // Unnecessary to collect data if it's an ACK or NACK
            if ((Message == ACK) || (Message == NACK))
                return;
            // Do the loop if there's no timeout or the timeout cycle still exists
            while ((!TimeOut) && (TimeOutCycleCountDown > 0) && (bytecount < MaxBytes))
            {
                // Attempt to read from COM port
                if (1 == ComPort.Read(byteArrayBuffer, bytecount, 1))
                {
                    bytecount++;
                }
                else
                {
                    // If no response, set error and return
                    error = ReaderError.NO_RESPONSE;
                    return;
                }
                // Check if end of packet has been received
                if (IsComplete(byteArrayBuffer, bytecount))
                {
                    TimeOut = true;
                }
            }
            // Copy data response and dectect ESC characters
            int j = 0;
            for (int i = 0; i < bytecount; i++)
            {
                if (IsInsidePacket(byteArrayBuffer, i))
                {
                    if (byteArrayBuffer[i] == ESC)
                    {
                        switch (byteArrayBuffer[i + 1])
                        {
                            case ESC_END: // DBDC = C0
                                byteArrayReturn[j++] = 0xC0;
                                i += 2;
                                break;
                            case ESC_ESC: // DBDD = DB
                                byteArrayReturn[j++] = 0xDB;
                                i += 2;
                                break;
                            default:
                                break;
                        }
                    }
                }
                byteArrayReturn[j++] = byteArrayBuffer[i];
            }
            Response = HexStringDecoder.ByteArrayToString(byteArrayReturn, j);
            #endregion

            /*-----------------------------------*
             *        Handling NACK reponse      *
             *-----------------------------------*/

            #region Handling NACK reponse
            // Prepare to resend the command
            if ((Response != null) && (Response.Length >= NACK.Length) &&
                (Response.Substring(0, NACK.Length) == NACK) && (RetryNACK-- > 0))
            {
                // Terminate all the running command after a number of retries when receiving NACK response
                if (RetryNACK == 0)
                {
                    goto SendLabel;
                }
            }
            #endregion
        }
        
        #endregion

        /*****************************************************************************/
        /*                                                                           */
        /*          Smart Card and Reader Communication                              */
        /*                                                                           */
        /*****************************************************************************/

        #region Smart Card

        // Validate responses
        void ResponseValidation(PacketParser packet)
        {
            string Response = null;
            // Infinite Loop detection 
            if ((packet.ErrorCode == PacketParser.ErrorType.Timeout)
                || (packet.ErrorCode == PacketParser.ErrorType.NoResponse)
                || (packet.ErrorCode == PacketParser.ErrorType.NACK))
            {
                error = ReaderError.NO_RESPONSE;
            }
            // Other error detection
            else if (packet.ErrorCode != PacketParser.ErrorType.None)
            {
                error = ReaderError.UNKNOWN_ERROR;
                SendReceive(ACK, ref Response, TestTimeOut);
            }
            // Correct data response
            else
            {
                error = ReaderError.NONE;
                SendReceive(ACK, ref Response, TestTimeOut);
            }
        }

        // Connect reader
        internal override bool ConnectReader()
        {
            error = ReaderError.NONE;
            string Response = null;
            // Set port properties
            port = "COM1";
            baudrate = 9600;
            // If reader is already on, no need to turn on again
            if (IsSerialPortOn)
            {
                error = ReaderError.ALREADY_CONNECTED;
                return true;
            }
            try
            {
                // Open COM port
                InitializeComPort(baudrate, System.IO.Ports.Handshake.None);
                Config_Init = PowerOnReader;
                // Collect number of Retries if NACK receive
                NumberOfRetries = (int)Convert.ToByte(Config_Init.Substring(12, 2));
                // Send connect command to reader
                SendReceive(Config_Init, ref Response, InitializationTimeOut);
                // Make sure board connected
                Packet.GetPacket(Response);
                ResponseValidation((PacketParser)Packet);
                if (Packet.ErrorCode == PacketParser.ErrorType.None)
                {
                    IsSerialPortOn = true;
                    return true;
                }
                else
                {
                    IsSerialPortOn = false;
                    error = ReaderError.PORT_CONNECTION_FAILED;
                    if (ComPort.IsOpen)
                    {
                        ComPort.Close();
                    }
                    return false;
                }
            }
            catch
            {
                if (ComPort.IsOpen)
                {
                    ComPort.Close();
                }
                error = ReaderError.PORT_CONNECTION_FAILED;
                return false;
            }
        }

        // Disconnect reader
        internal override bool DisconnectReader()
        {
            error = ReaderError.NONE;
            // Cannot disconnect reader if sending APDU
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Reader is already off
            else if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return true;
            }
            // Close Port, reset port and board flags
            ComPort.Close();
            IsSerialPortOn = false;
            return true;
        }

        // Reset reader
        internal override bool ResetReader()
        {
            error = ReaderError.NONE;
            // Disconnect then connect reader
            if (IsSerialPortOn)
            {
                if (DisconnectReader())
                {
                    return false;
                }
            }
            return ConnectReader();
        }

        // Update status
        bool UpdateStatus()
        {
            error = ReaderError.NONE;
            // Cannot update status of reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // Cannot update status if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Cannot update status if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return false;
            }
            // Create status command string
            string StatusCmdStr = CheckStatus + SlotID + "FFFFCO";
            try
            {
                // Reset activated flag in case card is no longer activated
                CardActivated = false;
                // Send command to reader
                string Response = null;
                SendReceive(StatusCmdStr, ref Response, InitializationTimeOut);
                // Parse Response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If error, card is in error mode
                if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    error = ReaderError.UNKNOWN_ERROR;
                }
                else
                {
                    // Decode card's response
                    switch (HexStringDecoder.StringToByteArray(Response)[4 + 3]) // first 3 bytes are ACK or NACK characters
                    {
                        case NOCARD:
                            atr = null;
                            SlotStatus = NOCARD;
                            break;
                        case PRESENT:
                            atr = null;
                            SlotStatus = PRESENT;
                            break;
                        case ACTIVATED:
                            SlotStatus = ACTIVATED;
                            CardActivated = true;
                            break;
                        default:
                            atr = null;
                            SlotStatus = ERROR;
                            return false;
                    }
                }
                return true;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Send User SlotStatus after status update
        internal override int GetStatus()
        {
            UpdateStatus();
            return SlotStatus;
       }

        // Activate card
        internal override bool ActivateCard()
        {
            error = ReaderError.NONE;
            // Cannot activate card if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // If resetting card, the following will have already been checked
            if (!ResettingCard)
            {
                // Cannot activate card if sending APDU
                if ((!APDUCommandComplete))
                {
                    error = ReaderError.TEST_RUNNING;
                    return false;
                }
                UpdateStatus();
                // No need to activate already activated card
                if (SlotStatus == ACTIVATED || (ActivatingSmartCard))
                {
                    error = ReaderError.ACTIVATING_CARD;
                    return true;
                }
                // Cannot activate card if there is no card
                else if (SlotStatus != PRESENT)
                {
                    error = ReaderError.NO_CARD_INSERTED;
                    return false;
                }
            }
            // Set activation command string
            string Response = null;
            string testMode = "0103"; // sets to ISO mode ("01") and power at 5V ("03")
            string ActivationCmdStr = PowerOnSC + SlotID + testMode + "FFFFC0";
            try
            {
                // Set activation flag
                ActivatingSmartCard = true;
                // Send command to reader
                SendReceive(ActivationCmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If no response, deactivate
                if (Packet.ErrorCode == PacketParser.ErrorType.NoResponse
                    || Packet.ErrorCode == PacketParser.ErrorType.NACK)
                {
                    string DeactivationCmdStr = PowerOffSC + SlotID + "FFFFC0";
                    SendReceive(DeactivationCmdStr, ref Response, InitializationTimeOut);
                    Packet.GetPacket(Response);
                    ResponseValidation(Packet);
                    SlotStatus = ERROR;
                    error = ReaderError.NO_RESPONSE;
                    ActivatingSmartCard = false;
                    return false;
                }
                // Any other error, set status as error
                else if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    error = ReaderError.UNKNOWN_ERROR;
                    ActivatingSmartCard = false;
                    return false;
                }
                // No error means card is activated
                else
                {
                    SlotStatus = ACTIVATED;
                    atr = Response.Substring(14, Response.Length - 6 - 14);
                }
                // Update activation flags
                ActivatingSmartCard = false;
                CardActivated = true;
                return true;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Send PPS
        bool SendPPS()
        {
            error = ReaderError.NONE;
            // Cannot send PPS if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // Cannot send PPS if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Cannot send PPS if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return false;
            }
            UpdateStatus();
            // Cannot send PPS if card is deactivated
            if (SlotStatus != ACTIVATED)
            {
                error = ReaderError.CARD_DEACTIVATED;
                return false;
            }
            // Error checking for protocol
            else if ((!Protocol.Equals("00")) && (!Protocol.Equals("01")))
            {
                error = ReaderError.BAD_PROTOCOL;
                return false;
            }
            // Set command string for PPS
            string SetParametersCmdStr = SetParametersCommand + SlotID + Protocol + FiDi + "0000FFFFC0";
            try
            {
                // Send PPS to card
                string Response = null;
                SendReceive(SetParametersCmdStr, ref Response, InitializationTimeOut);
                // Parse Response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                return true;
            }
            catch
            {
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Send PPS with user defined parameters
        internal override bool SendPPS(byte protocol)
        {
            error = ReaderError.NONE;
            if(protocol != 1 && protocol != 0)
            {
                error = ReaderError.BAD_PROTOCOL;
                return false;
            }
            Protocol = HexStringDecoder.ByteToHex(protocol);
            return SendPPS();
        }

        // Deactivate card
        internal override bool DeactivateCard()
        {
            error = ReaderError.NONE;
            // Cannot deactivate card if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // If resetting card, the following has already been done
            if (!ResettingCard)
            {
                // Cannot deactivate if currently activating
                if (ActivatingSmartCard)
                {
                    error = ReaderError.ACTIVATING_CARD;
                    return false;
                }
                // Cannot deactivate if APDU is being sent
                else if ((!APDUCommandComplete))
                {
                    error = ReaderError.TEST_RUNNING;
                    return false;
                }
                UpdateStatus();
                // No need to deactivate if card is already deactivated
                if (SlotStatus == PRESENT)
                {
                    error = ReaderError.CARD_DEACTIVATED;
                    return true;
                }
                // Cannot deactivate if there is no card
                else if (SlotStatus != ACTIVATED)
                {
                    error = ReaderError.NO_CARD_INSERTED;
                    return false;
                }
            }
            // Create deactivation command string
            string DeactivationCmdStr = PowerOffSC + SlotID + "FFFFC0";
            try
            {
                // Set deactivating flag
                ActivatingSmartCard = true;
                // Send command to reader
                string Response = null;
                SendReceive(DeactivationCmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If error, assume card was not deactivated
                if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    atr = null;
                    error = ReaderError.UNKNOWN_ERROR;
                    ActivatingSmartCard = false;
                    return false;
                }
                else
                {
                    SlotStatus = PRESENT;
                    atr = null;
                }
                // Reset flags
                ActivatingSmartCard = false;
                CardActivated = false;
                return true;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Reset card
        internal override bool ResetCard()
        {
            error = ReaderError.NONE;
            // Reader must be on to reset card
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // Cannot reset card while sending APDU
            else if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Cannot reset card if activiating card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return true;
            }
            UpdateStatus();
            // Cannot reset card if not there
            if (SlotStatus == NOCARD)
            {
                error = ReaderError.NO_CARD_INSERTED;
                return false;
            }
            // Set reset flag
            ResettingCard = true;
            // Deactivate card if necessary, then reactivate
            if (CardActivated)
            {
                DeactivateCard();
            }
            bool ret = ActivateCard();
            ResettingCard = false;
            return ret;
        }

        // Checks to see if reader is Connected or not
        internal override bool IsReaderActive()
        {
            error = ReaderError.NONE;
            return IsSerialPortOn;
        }

        // Executes a Command given as a parameter
        internal override string ExecuteCommand(string commandCode, string data)
        {
            error = ReaderError.NONE;
            // Cannot send command if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return null;
            }
            // Cannot send command if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return null;
            }
            // Cannot send command if activiating card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return null;
            }
            // Create command string
            string CmdStr = "C0" + commandCode + SlotID + data + "FFFFC0";
            try
            {
                // Send command to reader
                string Response = null;
                SendReceive(CmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                Response = Packet.ResMsg;
               return Response;
            }
            catch
            {
                error = ReaderError.UNKNOWN_ERROR;
                return null;
            }
        }

        // Get current error
        internal override ReaderError Error
        {
            get
            {
                return error;
            }
        }

        // Get ATR
        internal override string ATR
        {
            get
            {
                return atr;
            }
        }

        // Send APDU to smart card
        internal override string SendAPDU(string apduText)
        {
            error = ReaderError.NONE;
            // Cannot send APDU if serial port is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return null;
            }
            // Cannot send APDU if another APDU is being sent
            else if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return null;
            }
            // Cannot send APDU if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return null;
            }
            UpdateStatus();
            // Cannot send APDU if card is deactivated
            if (SlotStatus != ACTIVATED)
            {
                error = ReaderError.CARD_DEACTIVATED;
                return null;
            }
            // Start Sending Procedure
            APDUCommandComplete = false;
            string Response = null;
            string APDUCmdStr = APDUCommand + SlotID + apduText + "FFFFC0"; ;
            // Send APDU string
            SendReceive(APDUCmdStr, ref Response, TestTimeOut);
            // Parse Response string
            Packet.GetPacket(Response);
            ResponseValidation(Packet);
            Response = Packet.resp;
            APDUCommandComplete = true;
            return Response;

        }
        #endregion

    }
}
